package unimelb.distSystems.game;

import java.util.ArrayList;

import unimelb.distSystems.game.elements.Light;
import unimelb.distSystems.game.elements.GUIPanel;
import unimelb.distSystems.game.elements.Sky;

import jme3tools.converters.ImageToAwt;

import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.BulletAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.system.AppSettings;
import com.jme3.system.Timer;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import unimelb.distSystems.game.players.*;
import unimelb.distSystems.project2.Car;
import unimelb.distSystems.project2.Configurations;
import unimelb.distSystems.project2.RacingSession;

public class GameEngine extends SimpleApplication implements ActionListener {

	/*
	 * Game elements
	 */
    private BulletAppState bulletAppState;
    private VehicleControl local_playercontrol;
    private ChaseCamera chaseCamera;
    private GUIPanel panel;
    private boolean allowPlayerControl=false;
    
    /*
     * Player variables
     */
    private LocalPlayer local_player;
    private long local_player_id;
    private ArrayList<RemotePlayer> remote_players = new ArrayList<RemotePlayer>();
    
    /*
     * Racing session variables
     */
    private RacingSession racing_session;

    public void launch(RacingSession racing_session,long local_player_id)
    {
    	this.racing_session=racing_session;
    	this.local_player_id=local_player_id;
    	
        AppSettings settings = new AppSettings(true);
        settings.setRenderer(AppSettings.LWJGL_OPENGL2);
        settings.setResolution(Configurations.gameScreenWidth,Configurations.gameScreenHeight);
        settings.setVSync(true);//Matched to real feeling of 60fps not over 9000
        settings.setTitle(Configurations.title);
        settings.setFullscreen(false);
        setSettings(settings);
        setShowSettings(false);
        start();
    }
    
    private void setupKeys() {
    	inputManager.clearMappings();
        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
        //inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
        //inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
        inputManager.addListener(this, "Lefts");
        inputManager.addListener(this, "Rights");
        inputManager.addListener(this, "Ups");
        inputManager.addListener(this, "Downs");
        //inputManager.addListener(this, "Space");
        //inputManager.addListener(this, "Reset");
    }

    @Override
    public void simpleInitApp() {
    	/*
    	 * Physics
    	 */
        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
        stateManager.attach(bulletAppState);
        
        if (settings.getRenderer().startsWith("LWJGL")) {
            BasicShadowRenderer bsr = new BasicShadowRenderer(assetManager, 512);
            bsr.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal());
            viewPort.addProcessor(bsr);
        }
        
        /*
         * Players
         */
        create_players();

        /*
         * Sky
         */
        Sky sky=new Sky();
        sky.setup(assetManager, rootNode);
        
        Light light=new Light();
        light.addLight(rootNode);
        light.addLight(rootNode);
        
        /*
         * Local camera
         */
        flyCam.setEnabled(false);
        chaseCamera=new ChaseCamera(cam, local_player.getCarNode());
        chaseCamera.setDefaultVerticalRotation(0.3f);
        chaseCamera.setDefaultDistance(10);
        chaseCamera.setSmoothMotion(true);
        
        /*
         * GUI
         */
        panel=new GUIPanel();
        panel.setup(assetManager,guiNode);
        
        inputManager.clearMappings();
        
        /*
         * Track
         */
        createOutsideTrack(racing_session.getTrack().getTrackID());
        
    }
    
    private void createOutsideTrack(long track_id)
    {
    	for(String[] track:Configurations.tracks)
    	{
    		if(Long.parseLong(track[0])==track_id)
    			this.createTerrain(track[3]);
    	}
    }
    
    private void create_players()
    {
    	for(int i=0;i<racing_session.getCars().size();i++)
    	{
    		Car this_car = racing_session.getCars().get(i);
    		if(this_car.getDriverPlayerID()!=local_player_id)
    		{
    			RemotePlayer this_remote_player = new RemotePlayer(this_car.getDriverPlayerID());
    			this_remote_player.buildPlayer(assetManager,rootNode, bulletAppState,this_car.getFilename());
                positionPlayer(this_remote_player,i);
                remote_players.add(this_remote_player);
    		}
    		else
    		{
    			local_player=new LocalPlayer(this.local_player_id);
    	        local_player.buildPlayer(assetManager,rootNode, bulletAppState,this_car.getFilename());
    	        positionPlayer(local_player,i);
    	        local_playercontrol=local_player.getPlayer();
    		}
    	}
    }

    private void positionPlayer(PlayerCar player,int player_index)
    {
    	float x=racing_session.getTrack().startPtX;
    	float y=racing_session.getTrack().startPtY;
    	float z=racing_session.getTrack().startPtZ;
    	float d=racing_session.getTrack().startPtDirection;

    	player.locate(x-player_index*5,y, z, d);
    }

    public void onAction(String binding, boolean value, float tpf) {
    	if(allowPlayerControl)
    		local_player.takeAction(binding, value, tpf);
    }

    private boolean someone_wins=false;
    @Override
    public void simpleUpdate(float tpf) {
    	RemoteCommunication.upload_local_details(local_player);
    	RemoteCommunication.download_remote_details(remote_players);
    	
    	chaseCamera.setDefaultHorizontalRotation(local_player.getDefaultHorizontalRotationAngle());
    	panel.setSpeed(-local_playercontrol.getCurrentVehicleSpeedKmHour());
    	panel.showTimeUsed(timer);
    	
    	if(allowPlayerControl==false&&timer.getTimeInSeconds()-5>0)
    	{
    		allowPlayerControl=true;
    		setupKeys();
    	}

    	local_playercontrol.brake(4f);

    	// game end detect
    	if(!someone_wins)
    	{
	    	float x=local_playercontrol.getPhysicsLocation(new Vector3f()).getX();
	    	float z=local_playercontrol.getPhysicsLocation(new Vector3f()).getZ();
	    	if(x<racing_session.getTrack().endPtX2&&x>racing_session.getTrack().endPtX1&&z<racing_session.getTrack().endPtY+1&&z>racing_session.getTrack().endPtY-1)
	    	{
	    		//game end response
	    		panel.showRacingResult("YOU LOSE!");
	    		someone_wins=true;
	    		
	    		//send a signal to server
	    	}
    	}
    	else
    	{
    		try {
    			//Dont mind the bullet state error :P
				Thread.sleep(5000);
			} catch (InterruptedException e) {}
    		this.destroy();
    	}
    }

    private void createTerrain(String map_filepath) {
        Texture heightMapImage = assetManager.loadTexture(map_filepath+"_height.png");
        
        Material matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
        matRock.setTexture("m_Alpha", assetManager.loadTexture(map_filepath+"_alpha.png"));
        
        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
        grass.setWrap(WrapMode.Repeat);
        matRock.setTexture("m_Tex1", grass);
        matRock.setFloat("m_Tex1Scale", 64f);
        
        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
        dirt.setWrap(WrapMode.Repeat);
        matRock.setTexture("m_Tex2", dirt);
        matRock.setFloat("m_Tex2Scale", 32f);
        
        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
        rock.setWrap(WrapMode.Repeat);
        matRock.setTexture("m_Tex3", rock);
        matRock.setFloat("m_Tex3Scale", 128f);        

        AbstractHeightMap heightmap = null;
        try {
            heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f);
            heightmap.load();

        } catch (Exception e) {
            e.printStackTrace();
        }

        TerrainQuad terrain = new TerrainQuad("terrain", 65, 512+1, heightmap.getHeightMap());
        terrain.setMaterial(matRock);
        terrain.setModelBound(new BoundingBox());
        terrain.updateModelBound();
        terrain.setLocalScale(new Vector3f(2, 2, 2));
        terrain.setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode.Receive);
        
        RigidBodyControl terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0);
        terrain.addControl(terrainPhysicsNode);
        rootNode.attachChild(terrain);
        
        bulletAppState.getPhysicsSpace().add(terrainPhysicsNode);
    }
}